Add noisify API with structured NoiseModel (issue #729)#743
Add noisify API with structured NoiseModel (issue #729)#743yi-hsiu-yang wants to merge 4 commits into
Conversation
Benchmark Results (Julia v1)Time benchmarks
Memory benchmarks
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #743 +/- ##
==========================================
+ Coverage 74.01% 74.13% +0.11%
==========================================
Files 111 111
Lines 7778 7802 +24
==========================================
+ Hits 5757 5784 +27
+ Misses 2021 2018 -3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Krastanov
left a comment
There was a problem hiding this comment.
this does not really seem to distinguish the single-qubit, two-qubit, idling, measurement, and reset noise -- all are just doing the same thing. A way to prove this works is to show a noisified circuit and where each different type of noise ended up in it.
| end | ||
|
|
||
| @testset "errors when idle requested without nqubits" begin | ||
| @test_throws ErrorException noisify([sHadamard(1)], DefaultNoiseModel()) |
There was a problem hiding this comment.
a test_throws should generally speaking not be this permissive -- you need to check it was actually the error you expected, not some completely unrelated error (thus hiding an actual bug, making the test actively harmful)
There was a problem hiding this comment.
Thanks for the review @Krastanov! I've pushed an update addressing both points, and would like to flag one design question that came up while working through the dispatch verification.
1. Tests now explicitly verify dispatch routing
I've added a new testset "noisify dispatches NoiseModel fields correctly" that exercises each dispatch path with visually distinguishable noise per field: different probabilities (0.01–0.05) and different Pauli biases (X-only / Y-only / Z-only / depolarizing), and asserts that each NoiseOp points back to the correct NoiseModel field, on the correct qubits.
Here's the noisified output for a 3-qubit, 5-operation circuit (5 ops → 15 ops):
[1] NoiseOp(PauliNoise(0.05, 0.0, 0.0), (2, 3)) ← idle on q2, q3
[2] NoiseOp(PauliNoise(0.01, 0.0, 0.0), (1,)) ← single_qubit on q1
[3] sHadamard(1)
[4] NoiseOp(PauliNoise(0.05, 0.0, 0.0), (3,)) ← idle on q3
[5] NoiseOp(PauliNoise(0.0, 0.02, 0.0), (1, 2)) ← two_qubit on q1, q2
[6] sCNOT(1, 2)
[7] NoiseOp(PauliNoise(0.05, 0.0, 0.0), (1, 3)) ← idle on q1, q3
[8] NoiseOp(PauliNoise(0.01, 0.0, 0.0), (2,)) ← single_qubit on q2
[9] sX(2)
[10] NoiseOp(PauliNoise(0.05, 0.0, 0.0), (2, 3)) ← idle on q2, q3
[11] NoiseOp(PauliNoise(0.0, 0.0, 0.03), (1,)) ← measurement on q1
[12] sMZ(1)
[13] NoiseOp(PauliNoise(0.05, 0.0, 0.0), (1, 2)) ← idle on q1, q2
[14] NoiseOp(PauliNoise(0.04, 0.04, 0.04), (3,)) ← reset on q3
[15] sMRZ(3)
Every gate is preceded by (a) idle noise on the non-affected qubits and (b) the op-specific noise from the matching NoiseModel field. The testset asserts both noise and indices for each NoiseOp and that the original gate is preserved at the expected position. Because each field uses a distinct PauliNoise parameter, any misrouted dispatch would fail the assertion.
2. @test_throws now matches the specific error message
Changed @test_throws ErrorException ... to @test_throws "nqubits must be provided" ..., so the test only passes on the intended error path rather than on any unrelated ErrorException that might be thrown.
3. Design question: should noise_model.reset apply to plain Reset?
I noticed that Reset (without measurement) currently falls through to the no-op fallback, it picks up noise_model.idle on non-affected qubits but not noise_model.reset:
julia> noisify([Reset(S"Z", [1])], model; nqubits=3)
2-element Vector{Any}:
NoiseOp(PauliNoise(0.05, 0.0, 0.0), (2, 3)) # idle on q2, q3
Reset(...) # no reset noise on q1The reset dispatch is currently noisify(g::AbstractResetMeasurement, ...), which covers sMRZ and friends but not Reset itself (which is AbstractOperation but not a subtype of AbstractResetMeasurement).
Is the intended semantics that noise_model.reset applies only to measure-and-reset operations (current behavior), or should it also apply to plain Reset?
Closes #729
This PR adds the
noisifyAPI for inserting noise operations into a circuit.What's implemented
noisify(circuit, noise)applies the same noise to all gate / measurement / reset operations.noisify(circuit, NoiseModel(...))allows different noise per operation type (single-qubit, two-qubit, measurement, reset, idle).nqubitsis provided, noise is also inserted on qubits not touched by each operation. A clear error is raised when idle noise is requested withoutnqubits.Tests
Added 9 tests in
test/test_noisify.jlcovering the simple API, structuredNoiseModel, idle noise insertion, error handling, insertion order, and pass-through behavior.Note
I'm a first-time contributor. All code was written by me; I used an AI assistant only to learn Julia syntax and discuss design choices conceptually, not to generate the implementation. Happy to adjust based on review feedback.